#ifndef AVALON_SERVICE_HPP
#define AVALON_SERVICE_HPP
#include <boost/call_traits.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/tuple/tuple.hpp>
#include "Avalon/Serialization/ShuttleTuple.hpp"
#include "Avalon/Services/Message.hpp"
#include "Avalon/Services/ServiceNode.hpp"
#include "Avalon/Threading/Async.hpp"

namespace Avalon {
namespace Services {

  /*! \class ServiceMessage
   *  \brief Base class for a Request or Response Message.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  class ServiceMessage : public Message {
    public:
      virtual ~ServiceMessage();

      //! Returns this Message's request id.
      virtual int GetRequestId() = 0;

      //! Returns <code>true</code> iff this is a Response Message.
      virtual bool IsResponseMessage() = 0;

      //! Sets an Async to the result of this Request/Response.
      /*!
        \param async The Async to receive the result of this Request/Response.
      */
      virtual void SetAsync(const Threading::BaseAsyncPtr& async);
  };

  template<typename T, typename R, typename A0 = Detail::NullType,
    typename A1 = Detail::NullType, typename A2 = Detail::NullType,
    typename A3 = Detail::NullType, typename A4 = Detail::NullType,
    typename A5 = Detail::NullType, typename A6 = Detail::NullType,
    typename A7 = Detail::NullType, typename A8 = Detail::NullType,
    typename A9 = Detail::NullType, typename A10 = Detail::NullType>
  class Service {};

  template<typename T, typename R>
  class Service<T, R> {
    public:
      typedef T Type;
      typedef R ReturnType;
      typedef typename Threading::Async<ReturnType>::Ptr ResponsePtr;
      typedef boost::tuple<> Parameters;

      static int InitializeService(const char* requestName,
          const char* responseName) {
        Serialization::DataShuttle::RegisterType<Service::Request>(
          requestName, 0);
        Serialization::DataShuttle::RegisterType<Service::Response>(
          responseName, 0);
        return 0;
      }

      class Request : public ServiceMessage {
        public:
          typedef boost::signals2::signal<void (const RequestToken<Service>&)>
            Signal;

          Request() {}

          Request(int requestId)
              : m_requestId(requestId) {}

          virtual ~Request() {}

          virtual int GetRequestId() {
            return m_requestId;
          }

          virtual bool IsResponseMessage() {
            return false;
          }

        protected:
          virtual void EmitSignal(boost::signals2::signal_base* signal,
              ServiceNode* serviceNode, IO::Channel* channel) {
            (*static_cast<Signal*>(signal))(RequestToken<Service>(serviceNode,
              channel, m_requestId));
          }

          virtual void Shuttle(Serialization::DataShuttle* shuttle,
              unsigned int version) {
            shuttle->Shuttle("request_id", m_requestId);
          }

        private:
          friend class Serialization::DataShuttle;
          int m_requestId;
      };

      class Response : public ServiceMessage {
        public:
          Response() {}

          Response(int requestId, const ReturnType& result)
              : m_requestId(requestId),
                m_result(result) {}

          virtual ~Response() {}

          virtual int GetRequestId() {
            return m_requestId;
          }

          virtual bool IsResponseMessage() {
            return true;
          }

          virtual void SetAsync(const Threading::BaseAsyncPtr& async) {
            boost::static_pointer_cast<Threading::AsyncOwner<ReturnType> >(
              async)->SetResult(m_result);
          }

          const ReturnType& Get() {
            return m_result;
          }

        protected:
          virtual void EmitSignal(boost::signals2::signal_base* signal,
              ServiceNode* serviceNode, IO::Channel* channel) {
            assert(false);
          }

          virtual void Shuttle(Serialization::DataShuttle* shuttle,
              unsigned int version) {
            shuttle->Shuttle("request_id", m_requestId);
            shuttle->Shuttle("result", m_result);
          }

        private:
          friend class Serialization::DataShuttle;
          int m_requestId;
          ReturnType m_result;
      };
  };

  #ifndef AVALON_SERVICE_PARAMETERS
    #define AVALON_SERVICE_PARAMETERS 10
  #endif

  #define AVALON_SERVICE_PASS_PARAMETER(z, n, a)                               \
    BOOST_PP_COMMA_IF(n) typename boost::call_traits<a##n>::param_type

  #define AVALON_SERVICE_DEFINE_PARAMETER(z, n, q)                             \
    BOOST_PP_COMMA_IF(n) typename boost::call_traits<q##n>::param_type a##n

  #define AVALON_SERVICE_GET_TUPLE_COMMA_COPY(z, n, a)                         \
    BOOST_PP_COMMA_IF(n) boost::get<n>(a)

  #define AVALON_SERVICE_PARAMETERS_COMMA_COPY(z, n, a)                        \
    BOOST_PP_COMMA_IF(n) a ## n

  #define AVALON_SERVICE_PARAMETER_TYPE(z, n, a)                               \
    struct Parameter##n {                                                      \
      typedef a##n Type;                                                       \
    };

  #define BOOST_PP_LOCAL_MACRO(n)                                              \
  template<typename T, typename R, BOOST_PP_ENUM_PARAMS(n, typename A)>        \
  class Service<T, R, BOOST_PP_ENUM_PARAMS(n, A)> {                            \
    public:                                                                    \
      typedef T Type;                                                          \
      typedef R ReturnType;                                                    \
      typedef typename Threading::Async<ReturnType>::Ptr ResponsePtr;          \
      typedef boost::tuple<BOOST_PP_ENUM_PARAMS(n, A)> Parameters;             \
                                                                               \
      BOOST_PP_REPEAT(n, AVALON_SERVICE_PARAMETER_TYPE, A)                     \
                                                                               \
      static int InitializeService(const char* requestName,                    \
          const char* responseName) {                                          \
        Serialization::DataShuttle::RegisterType<Service::Request>(            \
          requestName, 0);                                                     \
        Serialization::DataShuttle::RegisterType<Service::Response>(           \
          responseName, 0);                                                    \
        return 0;                                                              \
      }                                                                        \
                                                                               \
      class Request : public ServiceMessage {                                  \
        public:                                                                \
          typedef boost::signals2::signal<void (const RequestToken<Service>&,  \
            BOOST_PP_REPEAT(n, AVALON_SERVICE_PASS_PARAMETER, A))> Signal;     \
                                                                               \
          Request() {}                                                         \
                                                                               \
          Request(int requestId,                                               \
              BOOST_PP_REPEAT(n, AVALON_SERVICE_DEFINE_PARAMETER, A))          \
              : m_requestId(requestId),                                        \
                m_parameters(BOOST_PP_REPEAT(n,                                \
                  AVALON_SERVICE_PARAMETERS_COMMA_COPY, a)) {}                 \
                                                                               \
          virtual ~Request() {}                                                \
                                                                               \
          virtual int GetRequestId() {                                         \
            return m_requestId;                                                \
          }                                                                    \
                                                                               \
          virtual bool IsResponseMessage() {                                   \
            return false;                                                      \
          }                                                                    \
                                                                               \
        protected:                                                             \
          virtual void EmitSignal(boost::signals2::signal_base* signal,        \
              ServiceNode* serviceNode, IO::Channel* channel) {                \
            (*static_cast<Signal*>(signal))(RequestToken<Service>(serviceNode, \
              channel, m_requestId), BOOST_PP_REPEAT(n,                        \
              AVALON_QUEUED_SIGNAL_HANDLER_GET_TUPLE, m_parameters));          \
          }                                                                    \
                                                                               \
          virtual void Shuttle(Serialization::DataShuttle* shuttle,            \
              unsigned int version) {                                          \
            shuttle->Shuttle("request_id", m_requestId);                       \
            shuttle->Shuttle("parameters", m_parameters);                      \
          }                                                                    \
                                                                               \
        private:                                                               \
          friend class Serialization::DataShuttle;                             \
          int m_requestId;                                                     \
          Parameters m_parameters;                                             \
      };                                                                       \
                                                                               \
      class Response : public ServiceMessage {                                 \
        public:                                                                \
          Response() {}                                                        \
                                                                               \
          Response(int requestId, const ReturnType& result)                    \
              : m_requestId(requestId),                                        \
                m_result(result) {}                                            \
                                                                               \
          virtual ~Response() {}                                               \
                                                                               \
          virtual int GetRequestId() {                                         \
            return m_requestId;                                                \
          }                                                                    \
                                                                               \
          virtual bool IsResponseMessage() {                                   \
            return true;                                                       \
          }                                                                    \
                                                                               \
          virtual void SetAsync(const Threading::BaseAsyncPtr& async) {        \
            boost::static_pointer_cast<Threading::AsyncOwner<ReturnType> >(    \
              async)->SetResult(m_result);                                     \
          }                                                                    \
                                                                               \
          const ReturnType& Get() {                                            \
            return m_result;                                                   \
          }                                                                    \
                                                                               \
        protected:                                                             \
          virtual void EmitSignal(boost::signals2::signal_base* signal,        \
              ServiceNode* serviceNode, IO::Channel* channel) {                \
            assert(false);                                                     \
          }                                                                    \
                                                                               \
          virtual void Shuttle(Serialization::DataShuttle* shuttle,            \
              unsigned int version) {                                          \
            shuttle->Shuttle("request_id", m_requestId);                       \
            shuttle->Shuttle("result", m_result);                              \
          }                                                                    \
                                                                               \
        private:                                                               \
          friend class Serialization::DataShuttle;                             \
          int m_requestId;                                                     \
          ReturnType m_result;                                                 \
      };                                                                       \
  };

  #define BOOST_PP_LOCAL_LIMITS                                                \
    (1, AVALON_SERVICE_PARAMETERS)
  #include BOOST_PP_LOCAL_ITERATE()

  #define AVALON_DECLARE_SERVICE(Type, Name, ReturnType, ...)                  \
    struct Type##Token {};                                                     \
    typedef ::Avalon::Services::Service<Type##Token, ReturnType, __VA_ARGS__>  \
      Type;                                                                    \
    namespace {                                                                \
      int Type##AVALON_UNIQUE_NAME = Type::InitializeService(Name ".Request",  \
        Name ".Response");                                                     \
    }
}
}

#endif // AVALON_SERVICE_HPP
